package com.acme.net;

import java.io.*;
import java.util.*;
import java.net.*;

import com.acme.bankapp.service.bank.*;
import com.acme.bankapp.exceptions.*;

public class BankServer {
	private ServerSocket providerSocket = null;
	private Socket connection = null;
	private ObjectOutputStream out = null;
	private ObjectInputStream in = null;
	private String message = "";
	
	private BankService bankService;
	
	private static String OK = "OK";
	private static String noAuth = "Error: No auth";
	private static String authFailed = "Error: Auth failed";
	private static String argsError = "Error: Not enough args";
	private static String clientExists = "Error: client already exists";
	private static String saveDbWarning = "Warning: Failed to save db";
	private static String help =
			"help - show this help\n" +
			"auth <login> <password> - try to authorize\n" +
			"pwd <password> - change user password\n" +
			"stat - dump db\n" +
			"add <string> - add db entry";
	
	private class Parsers {
		private String login = "root";
		private String password = "verystrongpassword";
		private boolean auth = false;
		
		public abstract class MessageParser {
			private String command = "";
			
			protected MessageParser(String command) {
				this.command = command;
			}
			
			public boolean checkParser(String command) {
				return this.command.equals(command);
			}
			
			public abstract String parse(String[] args);
		}
		
		private LinkedList<MessageParser> parsers = new LinkedList<MessageParser>();
		
		private class AuthParser extends MessageParser {
			public AuthParser() {
				super("auth");
			}
			
			public String parse(String[] args) {
				if(args[1].equals(login) && args[2].equals(password)) {
					auth = true;
					return OK;
				} else {
					return authFailed;
				}
			}
		}
		
		private class StatParser extends MessageParser {
			public StatParser() {
				super("stat");
			}
			
			public String parse(String[] args) {
				if(!auth) {
					return noAuth;
				}
				
				return bankService.toString();
			}
		}
		
		private class AddParser extends MessageParser {
			public AddParser() {
				super("add");
			}
			
			public String parse(String[] args) {
				if(!auth) {
					return noAuth;
				}
				
				if(args.length < 2) {
					return argsError;
				}
				
				try {
					bankService.addClientByString(args[1]);
					try {
						bankService.saveBank();
					} catch(IOException ex) {
						return saveDbWarning;
					}
					return OK;
				} catch (ClientExistsException ex) {
					return clientExists;
				}
			}
		}
		
		private class HelpParser extends MessageParser {
			public HelpParser() {
				super("help");
			}
			
			public String parse(String[] args) {
				return help;
			}
		}
		
		private class PwdParser extends MessageParser {
			public PwdParser() {
				super("pwd");
			}
			
			public String parse(String[] args) {
				if(!auth) {
					return noAuth;
				}
				
				if(args.length < 2) {
					return argsError;
				}
				
				password = args[1];
				
				return OK;
			}
		}
		
		public Parsers() {
			this.parsers.add(new AuthParser());
			this.parsers.add(new StatParser());
			this.parsers.add(new AddParser());
			this.parsers.add(new HelpParser());
			this.parsers.add(new PwdParser());
		}
		
		public final LinkedList<MessageParser> getParsers() {
			return this.parsers;
		}
	}
	
	private String processMessage(Parsers parsers, String str) {
		String[] args = str.split(" ");
		String command = args[0];
		
		for(Parsers.MessageParser parser : parsers.getParsers()) {
			if(parser.checkParser(command)) {
				return parser.parse(args);
			}
		}

		return "";
	}
	
	private void run() {
		Parsers parsers;
		
		try {
			// 1. creating a server socket
			
			// This code was in example lab, I only copied it here because I thank it should be OK, isn't it?
			// Next time I'll check sources from there
			// Fixed now, we really don't need that
			providerSocket = new ServerSocket(2004);
			// 2. Wait for connection
			System.out.println("Waiting for connection");
			connection = providerSocket.accept();
			System.out.println("Connection received from " + connection.getInetAddress().getHostName());
			// 3. get Input and Output streams
			out = new ObjectOutputStream(connection.getOutputStream());
			out.flush();
			in = new ObjectInputStream(connection.getInputStream());
			sendMessage("Connection successful");
			parsers = new Parsers();
			// 4. The two parts communicate via the input and output streams
			do {
				try {
					message = (String) in.readObject();
					System.out.println("client>" + message);
					sendMessage(processMessage(parsers, message));
				} catch (ClassNotFoundException classnot) {
					System.err.println("Data received in unknown format");
				}
			} while (!message.equals("bye"));
		} catch (IOException ioException) {
			ioException.printStackTrace();
		} finally {
			// 4: Closing connection
			try {
				in.close();
				out.close();
				providerSocket.close();
			} catch (IOException ioException) {
				ioException.printStackTrace();
			}
		}
	}

	private void sendMessage(final String msg) {
		try {
			out.writeObject(msg);
			out.flush();
			System.out.println("server>" + msg);
		} catch (IOException ioException) {
			ioException.printStackTrace();
		}
	}
	
	public void startServer(BankService bankService) {
		this.bankService = bankService;
		
		while(true) {
			run();
		}
	}
}
